import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { AsPropWarning } from "../components/AsPropWarning";
import { Pipe } from "../components/Pipe";

<SEO
	title="Disclosure"
	description="Accessible disclosure component for React"
/>

# Disclosure

<TOC>
	<TOCList>
		<TOCLink href="#disclosure-1">Disclosure</TOCLink>
		<TOCLink href="#disclosurebutton">DisclosureButton</TOCLink>
		<TOCLink href="#disclosurepanel">DisclosurePanel</TOCLink>
		<TOCLink href="#usedisclosurecontext">useDisclosureContext</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/disclosure
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#disclosure

A disclosure is a button that controls the visibility of a panel of content. When the content inside the panel is hidden, it is often styled as a typical push button with a right-pointing arrow or triangle to hint that activating the button will display additional content. When the content is visible, the arrow or triangle typically points down.

If you have a group of disclosures that stack vertically and exist within the same logical context, you may want to use [`@reach/accordion`](/accordion) instead.

## Installation

From the command line in your project directory, run `npm install @reach/disclosure` or `yarn add @reach/disclosure`. Then import the components that you need:

```bash
npm install @reach/disclosure
# or
yarn add @reach/disclosure
```

```js
import {
	Disclosure,
	DisclosureButton,
	DisclosurePanel,
} from "@reach/disclosure";
```

## Usage

```jsx
// jsx-demo
function Example() {
	return (
		<Disclosure>
			<DisclosureButton>Find out what lies beneath</DisclosureButton>
			<DisclosurePanel>Here I am! I am the buried treasure!</DisclosurePanel>
		</Disclosure>
	);
}
```

## Component API

### Disclosure

The wrapper component and context provider for a disclosure's button and panel components. A disclosure should never have more than one button or panel descendants.

```jsx
<Disclosure>
	<DisclosureButton>Do a thing</DisclosureButton>
	<DisclosurePanel>...</DisclosurePanel>
</Disclosure>
```

It is important to note that the `Disclosure` component doesn't actually render a DOM node, so there is no way to add styles to a disclosure wrapper directly. If you want your disclosure to have a wrapper element you can nest one directly.

```jsx
<Disclosure>
	<div style={{ padding: 10 }}>
		<DisclosureButton>Do a thing</DisclosureButton>
		<DisclosurePanel>...</DisclosurePanel>
	</div>
</Disclosure>
```

#### Controlled Disclosure

If you want to control the disclosure's value, you can do so by passing [`open`](#disclosure-open) and [`onChange`](#disclosure-onchange) props.

```jsx
const [isOpen, setOpen] = React.useState(false);
return (
	<Disclosure open={isOpen} onChange={() => setOpen(!isOpen)}>
		<DisclosureButton />
		<DisclosurePanel />
	</Disclosure>
);
```

#### Disclosure Props

| Prop                                     | Type                       | Required |
| ---------------------------------------- | -------------------------- | -------- |
| [`children`](#disclosure-children)       | `node`                     | true     |
| [`defaultOpen`](#disclosure-defaultopen) | `boolean`                  | false    |
| [`id`](#disclosure-id)                   | `string` <Pipe /> `number` | false    |
| [`onChange`](#disclosure-onchange)       | `func`                     | false    |
| [`open`](#disclosure-open)               | `boolean`                  | false    |

##### Disclosure `children`

`children: React.ReactNode`

`Disclosure` expects to receive accept `DisclosureButton` and `DisclosurePanel` components as either direct children or descendants. It can also accept wrapper elements if desired, though it is not recommended to pass other arbitrary components within a disclosure in most cases.

```jsx
<div>
	{/* OK! */}
	<Disclosure>
		<DisclosureButton>Click Me</DisclosureButton>
		<DisclosurePanel>Collapse or open this content!</DisclosurePanel>
	</Disclosure>

	{/* Also OK! */}
	<Disclosure>
		<div>
			<DisclosureButton>Click Me</DisclosureButton>
			<DisclosurePanel>Collapse or open this content!</DisclosurePanel>
		</div>
	</Disclosure>

	{/* Probably confusing, you should avoid! */}
	<Disclosure>
		<div>Mary had a little lamb, little lamb, blah blah blah</div>
		<DisclosureButton>Click Me</DisclosureButton>
		<DisclosurePanel>Collapse or open this content!</DisclosurePanel>
		<blockquote>You miss 100% of the shots you don't take</blockquote>
	</Disclosure>
</div>
```

##### Disclosure `defaultOpen`

`defaultOpen?: boolean`

Whether or not an uncontrolled disclosure component should default to its `open` state on the initial render. Defaults to `false`.

##### Disclosure `id`

`id?: string | number`

An id used to assign aria and id attributes to nested `DisclosureButton` and `DisclosurePanel` components.

Since the Disclosure component itself does not render a DOM element, an `id` prop will not appear in the DOM directly as may be expected. Rather, we need to generate IDs for the panel and button based on a disclosure ID for aria compliance. If no `id` is passed we will generate descendant IDs for you.

```jsx
<Disclosure id="awesome-disclosure">
	<div>
		{" "}
		{/* no ID passed here! */}
		<DisclosureButton /> {/* <button id="awesome-disclosure--button" /> */}
		<DisclosurePanel /> {/* <div id="awesome-disclosure--panel" /> */}
	</div>
</Disclosure>
```

##### Disclosure `onChange`

`onChange?: () => void`

The callback that is fired when a disclosure's open state is changed.

##### Disclosure `open`

`open?: boolean`

The controlled open state of the disclosure. The `open` prop should be used along with `onChange` to create controlled disclosure components.

```jsx
const [open, setOpen] = React.useState(false);
return (
	<Disclosure open={open} onChange={() => setOpen(!open)}>
		<DisclosureButton>I have a secret</DisclosureButton>
		<DisclosurePanel>
			Ante rhoncus facilisis iaculis nostra faucibus vehicula ac consectetur
			pretium, lacus nunc consequat id viverra facilisi ligula eleifend, congue
			gravida malesuada proin scelerisque luctus est convallis.
		</DisclosurePanel>
	</Disclosure>
);
```

### DisclosureButton

The trigger button a user clicks to interact with a disclosure.

#### DisclosureButton CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-disclosure-button] {
}
[data-reach-disclosure-button][data-state="open"] {
}
[data-reach-disclosure-button][data-state="collapsed"] {
}
```

#### DisclosureButton Props

| Prop                                     | Type                          | Required |
| ---------------------------------------- | ----------------------------- | -------- |
| [`as`](#disclosurebutton-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#disclosurebutton-children) | `node`                        | true     |

##### DisclosureButton `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `DisclosureButton` what element to render. Defaults to `button`.

<AsPropWarning />

##### DisclosureButton `children`

`children: React.ReactNode`

Typically a text string that serves as a label for the disclosure button, though nested DOM nodes can be passed as well so long as they are valid children of interactive elements.

- **Further reading:** [_Be Wary of Nesting Roles_ by Adrian Roselli](https://adrianroselli.com/2016/12/be-wary-of-nesting-roles.html)

### DisclosurePanel

The collapsible panel in which inner content for a disclosure item is rendered.

#### DisclosurePanel CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-disclosure-panel] {
}
[data-reach-disclosure-panel][data-state="open"] {
}
[data-reach-disclosure-panel][data-state="collapsed"] {
}
```

#### DisclosurePanel Props

| Prop                                    | Type                          | Required |
| --------------------------------------- | ----------------------------- | -------- |
| [`as`](#disclosurepanel-as)             | `string` <Pipe /> `Component` | false    |
| [`children`](#disclosurepanel-children) | `node`                        | true     |

##### DisclosurePanel `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `DisclosurePanel` what element to render. Defaults to `div`.

<AsPropWarning />

##### DisclosurePanel `children`

`children: React.ReactNode`

Inner collapsible content for the disclosure item.

### useDisclosureContext

`function useDisclosureContext(): { id: string | undefined; panelId: string; open: boolean }`

A hook that exposes data for a given `Disclosure` component to its descendants.
